#include "mainwindow.h"


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QHBoxLayout *mainLayout = new QHBoxLayout;

    //左边总布局
    QVBoxLayout *leftLayout = new QVBoxLayout;
    //左边主编辑区
    mainEdit = new QTextEdit;
    mainEdit->setFont(QFont("Lucida Console", 12));
    QString expampleAsm = "LDA #9H\n"
                          "ADD #24H\n"
                          "SUB #8H\n"
                          "NOT\n"
                          "AND #7FH\n"
                          "OR  #02H\n"
                          "OUT\n"
                          "HLT";
    mainEdit->setText(expampleAsm);
    new AsmHighlighter(mainEdit->document());
    leftLayout->addWidget(mainEdit);
    leftLayout->addSpacing(8);
    //左边按钮栏
    QGridLayout *btnLayout = new QGridLayout;
    btnLayout->setSpacing(5);
    QPushButton *openBtn = new QPushButton("打开");
    openBtn->setMinimumWidth(25);
    connect(openBtn, &QPushButton::clicked, this, openAsmFile);
    QPushButton *saveBtn = new QPushButton("保存");
    saveBtn->setMinimumWidth(25);
    connect(saveBtn, &QPushButton::clicked, this, saveAsmFile);
    QPushButton *asmBtn  = new QPushButton("编译并运行");
    asmBtn->setMinimumWidth(25);
    connect(asmBtn, &QPushButton::clicked, this, asmAndRun);
    QPushButton *runBtn  = new QPushButton("编译并输出VHDL数组");
    runBtn->setMinimumWidth(25);
    connect(runBtn, &QPushButton::clicked, this, asmAndGetVhdlArray);
    QPushButton *vhdlBtn  = new QPushButton("编译并导出为VHDL文件");
    vhdlBtn->setMinimumWidth(25);
    connect(vhdlBtn, &QPushButton::clicked, this, outputVhdlFile);
    btnLayout->addWidget(openBtn, 0, 0);
    btnLayout->addWidget(saveBtn, 0, 1);
    btnLayout->addWidget(asmBtn, 1, 0, 1, 2);
    btnLayout->addWidget(runBtn, 2, 0, 1, 2);
    btnLayout->addWidget(vhdlBtn, 3, 0, 1, 2);
    leftLayout->addItem(btnLayout);
    mainLayout->addItem(leftLayout);
    //右边输出栏位
    outEdit = new QTextEdit;
    outEdit->setReadOnly(true);
    splitLine = "================================================";
    QString startInfo = "SAP-1汇编器\n"
                        "作者：蔡爽  版本：0.1.2  更新日期：2015.4.25\n"
                        "================================================\n"
                        "使用注意：\n"
                        "1.程序打开时自动载入例子程序\n"
                        "2.源程序只支持立即寻址，编译后会自动转换成直接寻址\n"
                        "3.汇编指令：LDA #num,ADD #num,SUB #num,AND #num,OR #num,NOT,OUT,HLT\n"
                        "4.ROM大小为16byte";
    outEdit->append(startInfo);
    mainLayout->addWidget(outEdit);
    mainLayout->setStretch(0,5);
    mainLayout->setStretch(1,12);
    //主窗口设置
    QWidget *centerWindow = new QWidget;
    setCentralWidget(centerWindow);
    centerWindow->setLayout(mainLayout);
    setMinimumSize(680,420);
    setWindowTitle("SAP-1汇编器");
}

void MainWindow::openAsmFile(){
    QString openDir;
    openDir = QFileDialog::getOpenFileName(this, "打开asm文件",NULL, "汇编文件(*.asm)");
    if(!openDir.isEmpty()){
        QFile file(openDir);
        if (file.open(QIODevice::ReadOnly | QIODevice::Text)){
            mainEdit->setText(file.readAll());
        }
    }
}

void MainWindow::saveAsmFile(){
    QString saveDir;
    saveDir = QFileDialog::getSaveFileName(this, "保存asm文件","new.asm", "汇编文件(*.asm)");
    if(!saveDir.isEmpty()){
        QFile file(saveDir);
        if (file.open(QIODevice::WriteOnly | QIODevice::Text)){
            QTextStream out(&file);
            out << mainEdit->toPlainText();
        }
    }
}

int MainWindow::run(){
    unsigned char i, a = 0, b = 0;
    for(i = 0; i < 16; i++){
        b = rom[rom[i] & 0x0f];
        switch (rom[i] >> 4) {
        case 0:
            a = b;
            break;
        case 1:
            a = a + b;
            break;
        case 2:
            a = a - b;
            break;
        case 3:
            a = a & b;
            break;
        case 4:
            a = a | b;
            break;
        case 5:
            a = ~b;
            break;
        case 14:
        case 15:
            return a;
        }
    }
    return a;
}

void MainWindow::asmAndRun(){
    if(getRom()){
        outEdit->append("编译成功，运行结果：");
        outEdit->append(QString::number(run(), 2) + "(二进制)，" + QString::number(run(), 10) + "(十进制)，" + QString::number(run(), 16) + "(十六进制)");
    }
}

void MainWindow::asmAndGetVhdlArray(){
    if(getRom()){
        outEdit->append("编译成功，VHDL数组：");
        outEdit->append(getVhdlArray());
    }
}

QString MainWindow::getVhdlArray(){
    int i;
    QString outStr;
    QStringList outStrList;
    for(auto romByte : rom){
        QString temCellStr = QString::number(romByte, 2);
        if(temCellStr.length() < 8){
            temCellStr = QString("0").repeated(8 - temCellStr.length()) + temCellStr;
        }
        outStrList.append(temCellStr);
    }
    outStr = "    TYPE rom_t IS ARRAY(0 TO 15) OF STD_LOGIC_VECTOR(7 DOWNTO 0);\n"
             "    CONSTANT rom_table : rom_t := (";
    for(i = 0; i < 15; i++){
        if(!(i % 4)) outStr += "\n        ";
        outStr += '"' + outStrList[i] + "\"," ;
    }
    outStr += '"' + outStrList[15] + "\");";
    return outStr;
}

bool MainWindow::getRom(){
    int romPtr = 0, romDataPtr = 0, i;
    for(i = 0; i < 16; i++){
        rom[i] = 0;
    }
    QVector<int> romData, insAddr;
    QHash<QString, unsigned char> insHash;
    insHash["LDA"] = 0;
    insHash["ADD"] = 1;
    insHash["SUB"] = 2;
    insHash["AND"] = 3;
    insHash["OR"]  = 4;
    insHash["NOT"] = 5;
    insHash["OUT"] = 14;
    insHash["HLT"] = 15;
    if(mainEdit->toPlainText().trimmed().isEmpty()) return false;
    QStringList codeLines = mainEdit->toPlainText().toUpper().split("\n", QString::SkipEmptyParts);
    outEdit->append(splitLine);
    try{
        for(auto codeLine : codeLines){
            QStringList ele = codeLine.split(" ", QString::SkipEmptyParts);
            if(!insHash.contains(ele[0])){
                outEdit->append(QString("未知的指令") + codeLine + "，第" + QString::number(romPtr + 1) + "行");
                throw 1;
            }
            rom[romPtr] = insHash[ele[0]] << 4;
            if(ele.length() == 2){
                ele[1].remove('#');
                bool convSuccess = true;
                if(ele[1].indexOf(QString("H"), -1) != -1){
                    ele[1].remove(QChar('H'));
                    romData.append(ele[1].toInt(&convSuccess, 16));
                }else{
                    romData.append(ele[1].toInt(&convSuccess, 10));
                }
                if(!convSuccess){
                    outEdit->append(QString("不正确的操作数") + codeLine + "，第" + QString::number(romPtr + 1) + "行");
                    throw 1;
                }
                insAddr.append(romPtr);
            }
            romPtr++;
        }
        if(codeLines.length() + romData.length() <= 16){
            romDataPtr = 16 - romData.length();
            for(i = 0; romDataPtr <= 15; i++, romDataPtr++){
                rom[romDataPtr] = romData[i];
                rom[insAddr[i]] |= romDataPtr;
            }
        }else{
            outEdit->append("编译结果超出ROM上限");
            throw 1;
        }
        return true;
    }catch(int x){
        outEdit->append("编译失败");
        return false;
    }
}

void MainWindow::outputVhdlFile(){
    QString saveDir;
    if(getRom()){
        outEdit->append("编译成功，正在生成VHDL文件");
        saveDir = QFileDialog::getSaveFileName(this, "保存VHDL文件","rom.vhdl", "VHDL文件(*.vhdl)");
        if(!saveDir.isEmpty()){
            QFile file(saveDir);
            if (file.open(QIODevice::WriteOnly | QIODevice::Text)){
                QTextStream out(&file);
                out << "LIBRARY IEEE;\n"
                "USE IEEE.STD_LOGIC_1164.ALL;\n"
                "USE IEEE.STD_LOGIC_UNSIGNED.ALL;\n\n"
                "ENTITY ROM IS\n"
                "PORT(\n"
                   "    ADDR : IN STD_LOGIC_VECTOR(3 DOWNTO 0);\n"
                   "    DATA : OUT STD_LOGIC_VECTOR(7 DOWNTO 0)\n"
                ");\n"
                "END ENTITY;\n\n"
                "ARCHITECTURE BEHAVIOR OF ROM IS\n";
                out << getVhdlArray();
                out << "\nBEGIN\n"
                       "    DATA <= rom_table(CONV_INTEGER(ADDR));\n"
                       "END ARCHITECTURE;\n";
                outEdit->append("VHDL文件已输出");
                return;
            }
        }
        outEdit->append("VHDL文件未输出");
    }
}

MainWindow::~MainWindow()
{

}
